//
//  GSSOperation.m
//  iGSS
//
//  Copyright 2010 Electronic Business Systems Ltd.
//
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//
//  http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
//

#import "GSSOperation.h"

#import "NSString+GSSAdditions.h"
#import "GSSCommunicator.h"
#import "GSSObject.h"

#import "NSData+Base64.h"
#import "JSON.h"

#include <CommonCrypto/CommonHMAC.h>

@interface GSSOperation (Private)

- (void)handleFetchResponse:(NSHTTPURLResponse *)resp withData:(NSData *)data;
- (void)handleUpdateResponse:(NSHTTPURLResponse *)resp withData:(NSData *)data;
- (void)handleMoveResponse:(NSHTTPURLResponse *)resp withData:(NSData *)data;
- (void)handleCreateResponse:(NSHTTPURLResponse *)resp withData:(NSData *)data;
- (void)handleSearchResponse:(NSHTTPURLResponse *)resp withData:(NSData *)data;

@end


//MARK: -

@implementation GSSOperation

@synthesize object;
@synthesize type;

@synthesize method;
@synthesize contentType;
@synthesize params;
@synthesize body;


//MARK: -

- (id)initWithGSSObject:(GSSObject *)anObject withType:(GSSOperationType)aType {
    if (self = [super init]) {
        object = [anObject retain];
        type = aType;
        
        [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
    }
    return self;
}

- (void)dealloc {
    [object release];
    
    [method release];
    [contentType release];
    [params release];
    [body release];
    
    [super dealloc];
}

- (NSString *)key {
    return [NSString stringWithFormat:@"%@ %@", self.method, object.uri];
}

//MARK: -

- (void)main {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    GSSCommunicator *communicator = [GSSCommunicator defaultCommunicator];
    NSString *username = communicator.username;
    NSData *token = communicator.token;
    
    // RFC 2626 formatted date
    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
	NSLocale *locale = [[[NSLocale alloc] initWithLocaleIdentifier:@"en-US"] autorelease];
	[formatter setLocale:locale];
	[formatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"GMT"]];
	[formatter setDateFormat:@"EEE, dd MMM yyyy HH:mm:ss 'GMT'"];
	NSString *date = [formatter stringFromDate:[NSDate date]];
	[formatter release];
    
    // Remove the "https://.../rest" prefix
    NSString *unversionedURI = [object.uri stringByDeletingURIQuery];
    NSRange range = [unversionedURI rangeOfString:@"/rest/"];
    NSString *path = [unversionedURI substringFromIndex:range.location + 5];
        
    // Generate the signature
    NSString *s = [NSString stringWithFormat:@"%@%@%@", self.method, date, path];
	const void *key = [token bytes];
	size_t keyLength = [token length];
	const void *buf = [s UTF8String];
	size_t bufLength = [s length];
	unsigned char macOut[20];
    CCHmac(kCCHmacAlgSHA1, key, keyLength, buf, bufLength, macOut);
	NSString *sig = [[NSData dataWithBytes:macOut length:20] base64EncodedString];    
	NSString *auth = [NSString stringWithFormat:@"%@ %@", username, sig];
    
    NSMutableString *uriParams = [NSMutableString string];
	for (NSString *name in self.params) {
		NSString *sep = [uriParams length] ? @"&" : @"?"; 
		NSString *val = [[self.params objectForKey:name]
						 stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
		[uriParams appendFormat:@"%@%@=%@", sep, name, val];
	}
        
	NSURL *url = [NSURL URLWithString:[object.uri stringByAppendingString:uriParams]];
    NSLog(@"%@ %@", self.method, url);

    NSMutableURLRequest *req = [[NSMutableURLRequest alloc] initWithURL:url];
	NSHTTPURLResponse *resp;
    NSError *error;

    [req setHTTPMethod:self.method];
	[req setValue:date forHTTPHeaderField:@"X-GSS-Date"];
	[req setValue:auth forHTTPHeaderField:@"Authorization"];
        
	if (self.contentType)
		[req setValue:self.contentType forHTTPHeaderField:@"Content-Type"];
	if (self.body)
		[req setHTTPBody:self.body];
        
    NSData *data = [NSURLConnection sendSynchronousRequest:req
										 returningResponse:&resp
													 error:&error];
    [req release];

    NSArray *args = [NSArray arrayWithObjects:resp, data, nil];
    
    [self performSelectorOnMainThread:@selector(responseWithArgs:)
                           withObject:args
                        waitUntilDone:NO];
    
    [pool release];
}


//MARK: -
//MARK: Runs on Main Loop

- (void)responseWithArgs:(NSArray *)args {
    NSInteger argsCount = [args count];
    NSHTTPURLResponse *resp = argsCount > 0 ? [args objectAtIndex:0] : nil;
    NSData *data = argsCount > 1 ? [args objectAtIndex:1] : nil;
    
    switch (type) {
        case GSSOperationTypeFetch:
            [self handleFetchResponse:resp withData:data];
            break;
        
        case GSSOperationTypeUpdate:
            [self handleUpdateResponse:resp withData:data];
            break;
        
        case GSSOperationTypeMove:
            [self handleMoveResponse:resp withData:data];
            break;
        
        case GSSOperationTypeCreate:
            [self handleCreateResponse:resp withData:data];
            break;
        
        case GSSOperationTypeSearch:
            [self handleSearchResponse:resp withData:data];
            break;            
    }
    
    [[GSSCommunicator defaultCommunicator] operationFinished:self];
}

- (void)handleFetchResponse:(NSHTTPURLResponse *)resp withData:(NSData *)data {
    switch ([resp statusCode]) {
        case 200:
        case 204:
            break;
            
        case 401:
        case 403:
            object.error = GSSOperationErrorAuthentication;
            return;
            
        default:
            object.error = GSSOperationErrorNetwork;
            return;
    }
    
    if (object.error)
        NSLog(@"%d %@", [resp statusCode], object.uri);
    
    if (object.type == GSSObjectTypeFile) {
        NSMutableDictionary *newMeta = [[[[resp allHeaderFields] objectForKey:@"X-Gss-Metadata"] JSONValue]
                                        mutableCopy];
        NSString *name = [newMeta objectForKey:@"name"];
        if (name) {
            // Fix name escaping
            [newMeta setObject:[[name stringByReplacingOccurrencesOfString:@"+" withString:@"%20"]
                                stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]
                        forKey:@"name"];
        }

        [object updateMeta:newMeta];
        [object saveData:data];
        [newMeta release];
    } else {
        NSString *json = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        [object updateMeta:[json JSONValue]];
        [json release];
    }

    object.fetchDate = [NSDate date];
    [object save];    
    object.parent.childrenUpdated = YES;
}

- (void)handleUpdateResponse:(NSHTTPURLResponse *)resp withData:(NSData *)data {
    switch ([resp statusCode]) {
        case 200:
        case 204:
            break;
        
        case 401:
        case 403:
            object.error = GSSOperationErrorAuthentication;
            break;
            
        default:
            object.error = GSSOperationErrorNetwork;
            break;
    }
    
    if (object.error)
        NSLog(@"%d %@", [resp statusCode], object.uri);

    object.parent.childrenUpdated = YES;
}

- (void)handleMoveResponse:(NSHTTPURLResponse *)resp withData:(NSData *)data {
    switch ([resp statusCode]) {
        case 200:
        case 204:
            break;
            
        case 401:
        case 403:
            object.error = GSSOperationErrorAuthentication;
            break;
            
        default:
            object.error = GSSOperationErrorNetwork;
            break;
    }
    
    if (object.error)
        NSLog(@"%d %@", [resp statusCode], object.uri);
    
    object.parent.childrenUpdated = YES;    
}

- (void)handleCreateResponse:(NSHTTPURLResponse *)resp withData:(NSData *)data {
    switch ([resp statusCode]) {
        case 200:
        case 201:
        case 204:
            break;
            
        case 401:
        case 403:
            object.error = GSSOperationErrorAuthentication;
            break;
            
        default:
            object.error = GSSOperationErrorNetwork;
            break;
    }
    
    if (object.error)
        NSLog(@"%d %@", [resp statusCode], object.uri);

    object.uploading = NO;    
    object.parent.childrenUpdated = YES;
    
}

- (void)handleSearchResponse:(NSHTTPURLResponse *)resp withData:(NSData *)data {
    switch ([resp statusCode]) {
        case 200:
        case 201:
        case 204:
            break;
            
        case 401:
        case 403:
            object.error = GSSOperationErrorAuthentication;
            break;
            
        default:
            object.error = GSSOperationErrorNetwork;
            break;
    }
    
    if (object.error)
        NSLog(@"%d %@", [resp statusCode], object.uri);
    
    NSString *json = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    [object updateMeta:[json JSONValue]];
    [json release];    
}

@end